<?php
/**
 * DooAcl class file.
 *
 * @author Leng Sheng Hong <darkredz@gmail.com>
 * @link http://www.doophp.com/
 * @copyright Copyright &copy; 2009 Leng Sheng Hong
 * @license http://www.doophp.com/license
 */

/**
 * Provides Access Control List feature to the application.
 *
 * <p>DooAcl performs authorization checks for the specified resource and action. It checks against the rules defined in acl.conf.php.</p>
 * <p>Only when the user is allowed by one of the rules, will he be able to access the action. 
 * If the user role cannot be found in both deny and allow list, he will not be able to access the action/resource</p>
 *
 * <p>Rules has to be defined in this way:</p>
 * <code>
 * # Allow member to access all actions in Sns and Blog resource.
 * $acl['member']['allow'] = array(
 * 				'SnsController'=>'*', 
 * 				'BlogController'=>'*', 
 * 			);
 * 
 * # Allow anonymous visitors for Blog index only.
 * $acl['anonymous']['allow'] = array(
 * 				'BlogController'=>'index', 
 * 			);
 *
 * # Deny member from banUser, showVipHome, etc.
 * $acl['member']['deny'] = array(
 * 				'SnsController'=>array('banUser', 'showVipHome'), 
 * 				'BlogController' =>array('deleteComment', 'writePost')
 * 			);
 *
 * # Admin can access all except Sns showVipHome
 * $acl['admin']['allow'] = '*';
 * $acl['admin']['deny'] = array(
 * 				'SnsController'=>array('showVipHome')
 * 			);
 *
 * # If member is denied, reroute to the following routes.
 * $acl['member']['failRoute'] = array(
 * 				//if not found this will be used
 * 				'_default'=>'/error/member',	
 * 				
 * 				//if denied from sns banUser
 * 				'SnsController/banUser'=>'/error/member/sns/notAdmin', 
 * 				
 * 				'SnsController/showVipHome'=>'/error/member/sns/notVip', 
 * 				'BlogController'=>'/error/member/blog/notAdmin' 
 * 			);
 * </code>
 *
 * <p>You have to assign the rules to DooAcl in bootstrap.</p>
 * <code>
 * # set rules
 * Doo::acl()->rules = $acl;
 * 
 * # The default route to be reroute to when resource is denied. If not set, 404 error will be displayed.
 * Doo::acl()->defaultFailedRoute = '/error';
 * </code>
 *
 * @author Leng Sheng Hong <darkredz@gmail.com>
 * @version $Id: DooAcl.php 1000 2009-08-23 20:46:49
 * @package doo.auth
 * @since 1.1
 */
class DooAcl{
    /**
     * Rules settings for the application ACL. Defined in acl.conf.php
     * @var array
     */
    public $rules;

    /**
     * Default route to be reroute to if no custome fail route is defined for a certain rule.
     * @var string|array
     */
    public $defaultFailedRoute = array('/error-default/failed-route/please-set-in-route', 404);

    /**
     * Check if the user Role is allowed to access the resource or action list or both.
     *
     * <code>
     * //Check if member is allowed for BlogController->post
     * Doo::acl()->isAllowed('member', 'BlogController', 'post' );
     *
     * //Check if member is allowed for BlogController
     * Doo::acl()->isAllowed('member', 'blog');
     * </code>
     * 
     * @param string $role Role of a user, usually retrieve from user's login session
     * @param string $resource Resource name (use Controller class name)
     * @param string $action Action name (use Method name)
     * @return bool
     */
    public function isAllowed($role, $resource, $action=''){
        if($action==''){
            return isset($this->rules[$role]['allow'][$resource]);
        }
        else{
            if(isset($this->rules[$role]['allow'][$resource])){
                $actionlist = $this->rules[$role]['allow'][$resource];

                if($actionlist==='*')
                    return true;
                else
                    return in_array($action, $actionlist);
            }
            else
                return false;
        }
    }

    /**
     * Check if the user Role is denied from the resource or action list or both.
     *
     * <code>
     * //Check if member is denied from BlogController->post
     * Doo::acl()->isDenied('member', 'BlogController', 'post' );
     *
     * //Check if member is denied from BlogController
     * Doo::acl()->isDenied('member', 'blog');
     * </code>
     *
     * @param string $role Role of a user, usually retrieve from user's login session
     * @param string $resource Resource name (use Controller class name)
     * @param string $action Action name (use Method name)
     * @return bool
     */
    public function isDenied($role, $resource, $action=''){
        if($action==''){
            return isset($this->rules[$role]['deny'][$resource]);
        }
        else{
            if(isset($this->rules[$role]['deny'][$resource])){
                $actionlist = $this->rules[$role]['deny'][$resource];

                if($actionlist==='*')
                    return true;
                else
                    return in_array($action, $actionlist);
            }
            else
                return false;
        }
    }

    /**
     * Check if the user's role is able to access the resource/action.
     * 
     * @param string $role Role of a user, usually retrieve from user's login session
     * @param string $resource Resource name (use Controller class name)
     * @param string $action Action name (use Method name)
     * @return array|string Returns the fail route if user cannot access the resource.
     */
    public function process($role, $resource, $action=''){
        if( $this->isDenied($role, $resource, $action) ){
            //echo 'In deny list';

            if(isset($this->rules[$role]['failRoute'])){
                $route = $this->rules[$role]['failRoute'];

                if(is_string($route)){
                    return array($route, 'internal');
                }
                else{
                    if(isset($route[$resource])){
                        return (is_string($route[$resource]))? array($route[$resource], 'internal') : $route[$resource] ;
                    }
                    else if(isset( $route[$resource.'/'.$action] )){
                        $rs = $route[$resource.'/'.$action];
                        return (is_string($rs))? array($rs, 'internal') : $rs;
                    }
                    else if(isset( $route['_default'] )){
                        return (is_string($route['_default']))? array($route['_default'], 'internal') : $route['_default'];
                    }
                    else{
                        return (is_string($this->defaultFailedRoute))? array($this->defaultFailedRoute, 404) : $this->defaultFailedRoute;
                    }
                }
            }
        }
        else if($this->isAllowed($role, $resource, $action)==false){
            //echo 'Not in allow list<br>';

            if(isset($this->rules[$role]['failRoute'])){
                $route = $this->rules[$role]['failRoute'];

                if(is_string($route)){
                        return array($route, 'internal');
                }
                else{
                    if(isset($route[$resource])){
                        return (is_string($route[$resource]))? array($route[$resource], 'internal') : $route[$resource] ;
                    }
                    else if(isset( $route[$resource.'/'.$action] )){
                        $rs = $route[$resource.'/'.$action];
                        return (is_string($rs))? array($rs, 'internal') : $rs;
                    }
                    else if(isset( $route['_default'] )){
                        return (is_string($route['_default']))? array($route['_default'], 'internal') : $route['_default'];
                    }
                    else{
                        return (is_string($this->defaultFailedRoute))? array($this->defaultFailedRoute, 404) : $this->defaultFailedRoute;
                    }
                }
            }
        }
    }
}

?>